// scaup - Scala up!
// (c) 2009, Normen Müller, normen.mueller@gmail.com  
// $Id: Parser.scala 31 2009-11-12 12:05:13Z normen.mueller $
package locutor.parsing.cia.ig

import java.io.File
import java.net.URL
import java.text.MessageFormat.{format}

import collection.mutable.HashMap

import scalaxx.path._

import scaup.Views._

import locutor.cia.ImpactGraph

/** An impact graph parser.
 * 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.3 */
private [ig] class Parser extends BaseParser {
  /* Fill in abstract defs*/
  type Tokens = Lexer
  val lexical: Tokens = new Lexer
  
  /* Configure lexical parsing */                                 
  lexical.reserved ++= List(
    "graph", "new", "set"
  ) ++ BaseParser.reserved

  lexical.delimiters ++= List(
    "(", ")", "{", "}",
    "-", "->", "<", ">",
    "@", "$", "#"
  ) ++ BaseParser.delimiters
  
  /* Define the grammar */  
  lazy val ig = graph
  
  lazy val graph = ("new" ~ "graph" ~ text ~ text) ~> (nodes >> edges) 
  
  lazy val nodes = ((("new" ~> elem_def) ~ elem_assign)*) ^^ {
    case nodes =>      
      val g = new ImpactGraph
      val m = HashMap.empty[String, String]
      for(node <- nodes) node match {
        case ((ty, as)) ~ ((id, label)) =>
          val url = (as get "url") match {
            //@todo reassess toArray
            case Some(u) => u.toArray(0).toURL 
            case None    => error("Fatal IG parsing error: '" + id + "' is missing URL attribute")
          }
          val xpath = (as get "xpath") match {
            //@todo reasses toArray
            //@todo eliminate asInstanceOf
            case Some(x) => XPath.parse(x.toArray(0)).asInstanceOf[LocationPath]
            case None    => error("Fatal IG parsing error: '" + id + "' is missing XPath attribute")
          }
          g + (id, url, xpath, (Map("type" -> Set(ty)) ++ (as - "url" - "xpath")))
          m + {(label, id)}
      }      
      (g, m)
  }
  
  lazy val edges = (p: (ImpactGraph, HashMap[String, String])) => ((("new" ~> node) ~ ("-" ~> elem_def <~ "->") ~ node)*) ^^ {
    case edges => 
      for(edge <- edges) edge match {
        case src ~ ((ty, as)) ~ tgt => 
          (p._1 \ p._2(src), p._1 \ p._2(tgt)) match {
          case (Some(s), Some(t)) => (s connectTo t)(as + {("type" -> Set(ty))})
          case _                  => error("Fatal IG parsing error: Some edge declaration is not syntax conform.")
        }
      }
      p._1
  }
  
  lazy val node = ("@" ~ "(") ~> text <~ ")"
  
  lazy val elem_def    = ":" ~> (text ~ constructor) ^^ { case ty ~ as => (ty, as) }
  
  lazy val elem_assign = ("#" ~> text) ~ (("=" ~ "@" ~ "(") ~> text <~ ")") ^^ { case id ~ label => (id, label) }
  
  lazy val constructor = ("(" ~ "$" ~ "=" ~ text ~ ",") ~> ((attribute <~ opt(","))+) <~ ")" ^^ { case attributes => Map() ++ attributes }

  lazy val attribute = (ident <~ "=") ~ (set | text ^^ {case text => List(text)} | numeric ^^ {case numeric => List(numeric)}) ^^ {
    case k ~ v => (k, Set() ++ v)
  }
  
  lazy val set = ("set" ~ "<") ~> (primitives <~ ">") ~ ("{" ~> (((string  | numeric) <~ opt(","))*) <~ "}") ^^ {case ty ~ vals => vals}

}